<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>js的call和bind和apply用法</title>
    <script src="../zujian/han/han.js"></script>
</head>
<body>
<script type="text/javascript">
//     在 javascript 中，call 和 apply 都是为了改变某个函数运行时的上下文（context）而存在的，换句话说，
//     就是为了改变函数体内部 this 的指向。
// JavaScript 的一大特点是，函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。

function fruits() {}

fruits.prototype = {
    color: "red",
    say: function() {
        document.write("<br/>============001==============<br/>")
        document.write("My color is " + this.color);
        document.write("<br/>============002==============<br/>")
    }
}

var apple = new fruits;
// apple.say();    //My color is red



// 但是如果我们有一个对象banana= {color : "yellow"}
// ,我们不想对它重新定义 say 方法，那么我们可以通过 call 或 apply 用 apple 的 say 方法：
banana = {
    color: "yellow"
};
//call和apply转换this指向对象
apple.say.call(banana);     //My color is yellow
apple.say.apply(banana);    //My color is yellow


// 所以，可以看出 call 和 apply 是为了动态改变 this 而出现的，
// 当一个 object 没有某个方法（本栗子中banana没有say方法），
// 但是其他的有（本栗子中apple有say方法），我们可以借助call或apply用其它对象的方法来操作

// 对于 apply、call 二者而言，作用完全一样，只是接受参数的方式不太一样。例如，有一个函数定义如下：

var func = function(arg1, arg2) {
    //后面两个参数是打印用的
  hwrite(arg1+","+arg2,9,10);
};
// 就可以通过如下方式来调用：



func.call(this,1, 2);
func.apply(this, [1, 2])


//数组之间的追加用apply

var array1 = [12 , "foo" , {name:"Joe"} , -2458];
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
// array1 值为  [12 , "foo" , {name:"Joe"} , -2458 , "Doe" , 555 , 100]
//     alert(JSON.stringify(array1));

// 获取数组中的最大值和最小值
var  numbers = [5, 458 , 120 , -215 ];
var maxInNumbers = Math.max.apply(Math, numbers);   //458
    // alert(maxInNumbers);
    maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458
// alert(maxInNumbers);


// 验证是否是数组（前提是toString()方法没有被重写过）
function isArray(obj){
    return Object.prototype.toString.call(obj) === '[object Array]' ;
}
//true
// alert(isArray([]));


// var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
// Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象，还有像调用 getElementsByTagName , document.childNodes 之类的，它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。
// 但是我们能通过 Array.prototype.slice.call 转换为真正的数组的带有 length 属性的对象，这样 domNodes 就可以应用 Array 下的所有方法了。



// 定义一个 log 方法，让它可以代理 console.log 方法，常见的解决方法是：

function log(msg)　{
    console.log(msg);
}
// log(1);    //1
// log(1,2);    //1
// 上面方法可以解决最基本的需求，但是当传入参数的个数是不确定的时候，上面的方法就失效了，这个时候就可以考虑使用 apply 或者 call，注意这里传入多少个参数是不确定的，所以使用apply是最好的，方法如下：

function log(){
    console.log.apply(console, arguments);
};
// log(1);    //1
// log(1,2);    //1 2



// 接下来的要求是给每一个 log 消息添加一个"(app)"的前辍，比如：

// log("hello world"); //(app)hello world
// 该怎么做比较优雅呢?这个时候需要想到arguments参数是个伪数组，通过 Array.prototype.slice.call 转化为标准数组，再使用数组方法unshift，像这样：

function log(){
    var args = Array.prototype.slice.call(arguments);
    args.unshift('(app)');

    console.log.apply(console, args);
};


// bind
// 在讨论bind()方法之前我们先来看一道题目：

var altwrite = document.write;
// altwrite("hello");
// 结果：Uncaught TypeError: Illegal invocation
// altwrite()函数改变this的指向global或window对象，导致执行时提示非法调用异常，正确的方案就是使用bind()方法：
altwrite.bind(document)("hello")
// 当然也可以使用call()方法：
altwrite.call(document, "hello")


// 绑定函数
// bind()最简单的用法是创建一个函数，使这个函数不论怎么调用都有同样的this值。
// 常见的错误就像上面的例子一样，将方法从对象中拿出来，然后调用，并且希望this指向原来的对象。
// 如果不做特殊处理，一般会丢失原来的对象。使用bind()方法能够很漂亮的解决这个问题：

this.num = 9;
var mymodule = {
    num: 81,
    getNum: function() {
        document.write(this.num);
    }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 因为在这个例子中，"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81




// bind() 方法与 apply 和 call 很相似，也是可以改变函数体内 this 的指向。
//
// MDN的解释是：bind()方法会创建一个新函数，称为绑定函数，当调用这个绑定函数时，绑定函数会以创建它时传入 bind()方法的第一个参数作为 this，传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
//
// 直接来看看具体如何使用，在常见的单体模式中，通常我们会使用 _this , that , self 等保存 this ，这样我们可以在改变了上下文之后继续引用到它。 像这样：

var foo = {
    bar : 1,
    eventBind: function(){
        var _this = this;
        $('.someClass').on('click',function(event) {
            /* Act on the event */
            console.log(_this.bar);     //1
        });
    }
}
// 由于 Javascript 特有的机制，上下文环境在 eventBind:function(){ } 过渡到 $('.someClass').on('click',function(event) { })
// 发生了改变，上述使用变量保存 this 这些方式都是有用的，也没有什么问题。当然使用 bind() 可以更加优雅的解决这个问题：
var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            /* Act on the event */
            console.log(this.bar);      //1
        }.bind(this));
    }
}
// 在上述代码里，bind() 创建了一个函数，当这个click事件绑定在被调用的时候，它的 this 关键词会被设置成被传入的值（这里指调用bind()时传入的参数）。因此，这里我们传入想要的上下文 this(其实就是 foo )，
// 到 bind() 函数中。然后，当回调函数被执行的时候， this 便指向 foo 对象。再来一个简单的栗子：
var bar = function(){
    console.log(this.x);
}
var foo = {
    x:3
}

bar(); // undefined

var func = bar.bind(foo);
func(); // 3
// 这里我们创建了一个新的函数 func，当使用 bind() 创建一个绑定函数之后，它被执行的时候，
// 它的 this 会被设置成 foo ， 而不是像我们调用 bar() 时的全局作用域。
//上面绑定之后相当于
//     var foo={
//         x:3,
//         bar:function () {
//             console.log(this.x);
//         }
//     }

// bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数（如果有的话）
// 作为bind()的参数写在this后面。当绑定函数被调用时，
// 这些参数会被插入到目标函数的参数列表的开始位置，传递给绑定函数的参数会跟在它们后面。

function list() {
    //slice是为了转换伪数组为数组
    return Array.prototype.slice.call(arguments);
}
document.write("<br/>=======1=======<br/>");
var list1 = list(1, 2, 3); // [1, 2, 3]
document.write(list1);
document.write("<br/>=======0-1=======<br/>");
// 预定义参数37
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // 37
// alert(list2);
// alert(isArray(list2));//true
var list3 = leadingThirtysevenList(1, 2, 3); // 37, 1, 2, 3
// alert(list3);


// 和setTimeout一起使用
function Bloomer() {
    this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
Bloomer.prototype.declare = function() {
    console.log('我有 ' + this.petalCount + ' 朵花瓣!');
};
// 1秒后调用declare函数
// setTimeout就是几秒后弹出的意思
Bloomer.prototype.bloom = function() {
    window.setTimeout(this.declare.bind(this), 100);
};

var bloo = new Bloomer();
bloo.bloom(); //我有 5 朵花瓣!


// 绑定函数也适用于使用new操作符来构造目标函数的实例。当使用绑定函数来构造实例，
// 注意：this会被忽略，但是传入的参数仍然可用。
function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype.toString = function() {
    document.write(this.x + ',' + this.y);
};

var p = new Point(1, 2);

p.toString(); // '1,2'


var emptyObj = {};
var YAxisPoint1 = Point.bind(emptyObj, 0/*x*/);
// alert(YAxisPoint1);// 实现中的例子不支持,

// 原生bind支持:
var YAxisPoint = Point.bind(null, 0/*x*/);//相当于初始化一个参数x为0

var axisPoint = new YAxisPoint(5);
// axisPoint.toString(); // '0,5'

// alert(axisPoint instanceof Point); // true
// alert(axisPoint instanceof YAxisPoint); // true
// alert(new Point(17, 42) instanceof YAxisPoint); // true


// 捷径
// bind()也可以为需要特定this值的函数创造捷径。
//
// 例如要将一个类数组对象转换为真正的数组，可能的例子如下：

var slice = Array.prototype.slice;
//这是一个伪数组
var fakeArray = {
    "0": "first",
    "1": "second",
    "2": "third",
    length: 3
};
// ...将伪数组变为数组方式1
var ts = slice.call(fakeArray);
document.write("<br/>======3========<br/>");
document.write(ts);
// 如果使用bind()的话，情况变得更简单：
document.write("<br/>======4========<br/>");
// ...将伪数组变为数组方式2
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
document.write(slice(fakeArray));
document.write("<br/>======5========<br/>");

//自己造bind函数  有缺陷
// 实现
// 上面的几个小节可以看出bind()有很多的使用场景，但是bind()函数是在 ECMA-262
// 第五版才被加入；它可能无法在所有浏览器上运行。这就需要我们自己实现bind()函数了。
//
// 首先我们可以通过给目标函数指定作用域来简单实现bind()方法：
Function.prototype.bind = function(context){//有缺陷
    self = this;  //保存this，即调用bind方法的目标函数
    return function(){
        return self.apply(context,arguments);
    };
};
// 考虑到函数柯里化的情况，我们可以构建一个更加健壮的bind()：
// 这次的bind()方法可以绑定对象，也支持在绑定的时候传参。
Function.prototype.bind = function(context){//有缺陷,没看懂别用
    var args = Array.prototype.slice.call(arguments, 1),
        self = this;
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return self.apply(context,finalArgs);
    };
};

//
// 继续，Javascript的函数还可以作为构造函数，那么绑定后的函数用这种方式调用时，
// 情况就比较微妙了，需要涉及到原型链的传递：
// 这是《JavaScript Web Application》一书中对bind()的实现：通过设置一个中转构造函数F，
// 使绑定后的函数与调用bind()的函数处于同一原型链上，
// 用new操作符调用绑定后的函数，返回的对象也能正常使用instanceof，因此这是最严谨的bind()实现。
Function.prototype.bind = function(context){//有缺陷,没看懂别用
    // slice() 方法返回一个新的数组对象，这一对象是一个由 begin 和 end 决定的原数组的浅拷贝（包括 begin，不包括end）。原始数组不会被改变。
    var args = Array.prototype.slice(arguments, 1),
        F = function(){},
        self = this,
        bound = function(){
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return self.apply((this instanceof F ? this : context), finalArgs);
        };

    F.prototype = self.prototype;
    bound.prototype = new F();
    return bound;
};

var arr=[1,2,3];
let slice1 = arr.slice(0,2);// 1,2,  从0索引 包头比包尾部
hwrite(slice1,14,15)


mymodule.getNum.bind(mymodule)();//81//测试自己造的bind



//将伪数组转化为数组
// let tttt11 = Array.prototype.slice.call(伪数组);





</script>
</body>
</html>